
#include <MultiplexD/DaemonInstance.h>
#include <Ape/MT/Mutex.h>

#include <MultiplexD/WorkerModel/MultiplexWorker.h>

namespace MultiplexD {
	static DaemonInstance* s_DaemonInstance = NULL;
	static Ape::MT::Mutex  s_DaemonInstanceMutex(L"Daemon instance mutex");
	
	DaemonInstance::DaemonInstance()
			: m_ArgsParser(NULL)
	{
		m_Options.Reset();
	}
	DaemonInstance::~DaemonInstance() {}
	
	DaemonInstance* DaemonInstance::Get() {
		if (s_DaemonInstance) {
			return s_DaemonInstance;
		}
		Ape::MT::MutexLock lock(s_DaemonInstanceMutex);
		if (!s_DaemonInstance)
			s_DaemonInstance = new DaemonInstance;
		return s_DaemonInstance;
	}
	
	const DaemonOptions& DaemonInstance::Options() const {
		return m_Options;
	}
	DaemonOptions& DaemonInstance::OptionsVolatile() {
		return m_Options;
	}
	void DaemonInstance::ReadConfiguration(const Ape::Collections::Array<Ape::String>& args) {
		m_ArgsParser = new Ape::CLI::ArgumentsParser(args);
		
		OptionsVolatile().ActAsDaemon = m_ArgsParser->IsSet(L"daemon");
		OptionsVolatile().WorkingDirectory = m_ArgsParser->StringValue(L"workdir", true, L".");
		OptionsVolatile().ConfigFile = m_ArgsParser->StringValue(L"config", true, L"etc/multiplex.conf");
		OptionsVolatile().LogFile = m_ArgsParser->StringValue(L"log", true, L"");
		OptionsVolatile().PidFile = m_ArgsParser->StringValue(L"pidfile", true, L"");
		OptionsVolatile().Verbose = m_ArgsParser->IsSet(L"verbose");
		
		OptionsVolatile().Resolve();
	}
	
	void DaemonInstance::AddDefaultWorkers() {
		{ // add signal handler
			DaemonAPI::AddWorkerData* data = new DaemonAPI::AddWorkerData;
			data->ShLibPath = L"lib/libMultiplexD_SignalHandlerWorker.so";
			data->ClassName = L"MultiplexD::SignalHandlerWorker::Worker";
			data->RegistrationName = L"!SignalHandler";
			Ape::Uuid emptyUuid;
			WorkerModel::MultiplexLetter letter = {WorkerModel::ltAddWorker, emptyUuid, data};
			
			MailBox().Send(letter);
		}
	}
	
	void DaemonInstance::StartUp() {
		if (Options().Verbose)
			Options().Dump();
		
		Logger().Init();
		
		AddDefaultWorkers();
		
		bool doShutdown = false;	
		while (!doShutdown) {
			using namespace WorkerModel;
			using namespace DaemonAPI;
			try {
				MultiplexLetter letter = MailBox().Receive();
				switch (letter.LetterType) {
					case ltShutdown: {
						doShutdown = true;
						HandleShutdownRequest(letter, letter.GetData<ShutdownData>());
						break;
					}
					case ltAddWorker: {
						HandleAddWorkerRequest(letter, letter.GetData<AddWorkerData>());
						break;
					}
				}
			}
			catch (Ape::Exception& exc) {
				HandleError(exc);
			}
		}
	}
	
	Logging::Logger& DaemonInstance::Logger() {
		return m_Logger;
	}
	
	void DaemonInstance::HandleShutdownRequest(const WorkerModel::MultiplexLetter& letter, DaemonAPI::ShutdownData* data) {
		ShutdownAllWorkers();
		m_Logger.Message(letter);
		m_Logger.Join();
	}
	
	void DaemonInstance::HandleAddWorkerRequest(const WorkerModel::MultiplexLetter& letter, DaemonAPI::AddWorkerData* data) {
		using Ape::String;
		using Ape::IO::FS::Path;
		using Ape::Ext::SharedLibrary;
		
		Path pathToLib = Options().ResolvePath(data->ShLibPath);
		String className = data->ClassName;
		String registrationName = data->RegistrationName;
		
		DEBUG(L"Adding another worker: [" + pathToLib.ToString() + L"] " + className + L" as " + registrationName);
		
		if (m_Workers.KeyExists(registrationName)) {
			throw Ape::EInvalidOperation(L"The worker with the name '" + registrationName + L"' already registered in the system");
		}
		
		SharedLibrary* lib = NULL;
		if (!m_ShLibs.KeyExists(pathToLib.ToString())) {
			lib = new SharedLibrary(pathToLib);
			m_ShLibs[pathToLib.ToString()] = lib;
		}
		else {
			lib = m_ShLibs[pathToLib.ToString()];
		}
		
		try {
			WorkerModel::MultiplexWorker* worker = (WorkerModel::MultiplexWorker*)lib->Activate(className);
			m_Workers[registrationName] = worker;
			m_WorkerNames << registrationName;
		}
		catch (Ape::Exception& exc) {
			m_ShLibs.RemoveAt(pathToLib.ToString());
			throw;
		}
	}
	
	void DaemonInstance::ShutdownAllWorkers() {
		DEBUG(L"Asking workers to shutdown");
		foreach (workerName, Ape::Collections::LinkedList<Ape::String>, m_WorkerNames) {
			// ask to shutdown here
			WorkerModel::MultiplexWorker* worker = m_Workers[workerName];
			worker->Stop();
		}
		DEBUG(L"waiting for shutdown");
		foreach (workerName, Ape::Collections::LinkedList<Ape::String>, m_WorkerNames) {
			// wait for everybody
			WorkerModel::MultiplexWorker* worker = m_Workers[workerName];
			worker->Join();
		}
		DEBUG(L"deleting workers");
		foreach (workerName, Ape::Collections::LinkedList<Ape::String>, m_WorkerNames) {
			// delete 'em
			WorkerModel::MultiplexWorker* worker = m_Workers[workerName];
			del(worker);
			m_Workers.RemoveAt(workerName);
		}
	}
	
	Ape::MT::MailBox<MultiplexD::WorkerModel::MultiplexLetter>& DaemonInstance::MailBox() {
		return m_MailBox;
	}
	
	void DaemonInstance::HandleError(Ape::Exception& exc) {
		exc._Dump(true);
	}
}
